package com.ruyuan.o2o.groupbuy.logcenter.es;

import com.ruyuan.o2o.groupbuy.logcenter.hbase.HBaseClient;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * es客户端执行器
 *
 * @author ming qian
 */
@Component
@Slf4j
public class ElasticsearchClient {

    @Resource
    private ElasticsearchConfig esConfig;

    @Resource
    private HBaseClient hBaseClient;

    /**
     * 创建索引
     *
     * @param serverName 日志服务名称
     */
    public Boolean createIndex(String serverName) {
        try {
            RestHighLevelClient restHighLevelClient = esConfig.esClient();
            GetIndexRequest getIndexRequest = new GetIndexRequest(serverName);

            if (restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT)) {
                log.error("服务日志索引已存在");
                return false;
            }
            log.info("服务【{}】日志索引正在创建", serverName);

            CreateIndexRequest createIndexRequest = new CreateIndexRequest(serverName);
            //  注意:随意缩小数值会导致聚合统计精度下降,可以将参数降低看下效果 例如调整到5和1
            createIndexRequest.settings(Settings.builder()
                    .put("index.number_of_shards", 10)
                    .put("index.number_of_replicas", 2)
            );

            Map<String, Object> properties = putProperties();

            // 加入mapping
            Map<String, Object> mapping = new HashMap<>(16);
            mapping.put("properties", properties);

            // 完成映射
            createIndexRequest.mapping(mapping);

            // 创建索引
            restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
            log.info("服务【{}】日志索引创建完成", serverName);
            return true;

        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("创建索引失败");
        }
    }

    /**
     * 把每个字段的配置，都放到一个properties map里去
     *
     * @return propertiesMap 属性参数map
     */
    private Map<String, Object> putProperties() {
        Map<String, Object> propertiesMap = new HashMap<>(16);
        propertiesMap.put("hostname", new HashMap<String, Object>(16) {
            private static final long serialVersionUID = 3448491319839375123L;

            {
                put("type", "keyword");
            }
        });
        propertiesMap.put("ip", new HashMap<String, Object>(16) {
            private static final long serialVersionUID = -1438391197914571210L;

            {
                put("type", "keyword");
            }
        });
        propertiesMap.put("serverName", new HashMap<String, Object>(16) {
            private static final long serialVersionUID = -1438391197914571210L;

            {
                put("type", "keyword");
            }
        });
        propertiesMap.put("date", new HashMap<String, Object>(16) {
            private static final long serialVersionUID = 3448491319839375123L;

            {
                put("type", "keyword");
            }
        });
        propertiesMap.put("level", new HashMap<String, Object>(16) {
            private static final long serialVersionUID = -8600729635618151961L;

            {
                put("type", "keyword");
            }
        });
        propertiesMap.put("class", new HashMap<String, Object>(16) {
            private static final long serialVersionUID = 8412422174840265326L;

            {
                put("type", "keyword");
            }
        });
        propertiesMap.put("businessId", new HashMap<String, Object>(16) {
            private static final long serialVersionUID = -5741579760535733638L;

            {
                put("type", "keyword");
            }
        });
        propertiesMap.put("invocationId", new HashMap<String, Object>(16) {
            private static final long serialVersionUID = 4290551559571394917L;

            {
                put("type", "keyword");
            }
        });
        return propertiesMap;
    }

    /**
     * 搜索es from size方式
     *
     * @param searchParameters 搜索请求参数
     * @param boolQueryBuilder 查询文件构造器
     */
    public SearchResultVo search(SearchParameters searchParameters, BoolQueryBuilder boolQueryBuilder) throws Exception {
        //  建立ES搜索参数
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(boolQueryBuilder);
        searchSourceBuilder.sort(new FieldSortBuilder("date").order(SortOrder.DESC));

        //  ES模拟DB查询分页
        if (searchParameters.getFrom() == 1) {
            searchSourceBuilder.from(searchParameters.getFrom() - 1);
        } else {
            //  前台跳页查询，例如前台查询第3页，实际上查询的是30+的数据
            searchSourceBuilder.from((searchParameters.getFrom() - 1) * 10);
        }
        searchSourceBuilder.size(searchParameters.getSize());

        //  执行搜索查询
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = esConfig.esClient().search(searchRequest, RequestOptions.DEFAULT);

        //  返回分页查询参数
        SearchResultVo esVo = new SearchResultVo();
        esVo.setCount(searchResponse.getHits().getTotalHits().value);

        //  因为ES不具备关系型数据库的分页查询，有可能出现可以查询到ES总数，但是无法命中数据
        //  这种情况多出现在前台自定义查询数量未满10条的数据
        if (esVo.getCount() != 0 && searchResponse.getHits().getHits().length == 0) {
            //  处理思路：有总数但是跳页查询不到，页数从头开始查询，可模拟实现分页效果
            searchSourceBuilder.from(0);
            searchSourceBuilder.size(10);
            searchResponse = esConfig.esClient().search(searchRequest, RequestOptions.DEFAULT);
        }

        List<SearchResult> paramList = new ArrayList<>();
        for (SearchHit searchHit : searchResponse.getHits().getHits()) {
            SearchResult result = new SearchResult();

            result.setId(searchHit.getId());
            result.setIndex(searchHit.getIndex());
            result.setHostname(searchHit.getSourceAsMap().get("hostname") != null
                    ? searchHit.getSourceAsMap().get("hostname").toString()
                    : null);
            result.setIp(searchHit.getSourceAsMap().get("ip") != null
                    ? searchHit.getSourceAsMap().get("ip").toString()
                    : null);
            result.setServerName(searchHit.getSourceAsMap().get("serverName") != null
                    ? searchHit.getSourceAsMap().get("serverName").toString()
                    : null);
            result.setDate(searchHit.getSourceAsMap().get("date") != null
                    ? searchHit.getSourceAsMap().get("date").toString()
                    : null);
            result.setLevel(searchHit.getSourceAsMap().get("level") != null
                    ? searchHit.getSourceAsMap().get("level").toString()
                    : null);
            result.setClassName(searchHit.getSourceAsMap().get("className") != null
                    ? searchHit.getSourceAsMap().get("className").toString()
                    : null);
            result.setInvocationId(searchHit.getSourceAsMap().get("invocationId") != null
                    ? searchHit.getSourceAsMap().get("invocationId").toString()
                    : null);
            result.setBusinessId(searchHit.getSourceAsMap().get("businessId") != null
                    ? searchHit.getSourceAsMap().get("businessId").toString()
                    : null);
            //  查询hbase中的完整日志
            String logContent = hBaseClient.queryTable(searchHit.getId());
            result.setLog(logContent);

            paramList.add(result);
        }
        //  分页展示列表
        esVo.setSearchResultList(paramList);
        return esVo;
    }
}
